"""
自动故障分析服务
将故障检测结果自动转换为根因分析，实现检测-分析-修复的完整自动化流程
"""

import os
import sys
import logging
from typing import List, Dict, Optional
from datetime import datetime

# 添加roothealer模块到路径
current_dir = os.path.dirname(__file__)
roothealer_dir = os.path.join(current_dir, '..', 'roothealer')
sys.path.insert(0, roothealer_dir)  # 使用insert(0, ...)确保优先搜索

# 设置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# 尝试多种导入方式
FaultMatcher = None
Neo4jHandler = None
generate_analysis = None
generate_markdown = None
save_markdown = None
execute_solution_code = None
extract_solution1_code = None
load_script_by_code_id = None
GraphRAGRetriever = None

# 方法1: 直接导入
try:
    from core.roothealer.fault_matcher import FaultMatcher
    from core.roothealer.neo4j_handler import Neo4jHandler
    from core.roothealer.qwen_generator import generate_analysis
    from core.roothealer.markdown_generator import generate_markdown, save_markdown
    from core.roothealer.ansible_executor import execute_solution_code, extract_solution1_code
    from core.roothealer.knowledge_loader import load_script_by_code_id
    from core.roothealer.graphrag_retriever import GraphRAGRetriever
    logger.info("✅ 成功导入roothealer模块（方法1）")
except ImportError as e1:
    logger.warning(f"方法1导入失败: {e1}")
    
    # 方法2: 使用importlib动态导入
    try:
        import importlib.util
        
        modules_to_load = [
            ('fault_matcher', 'FaultMatcher'),
            ('neo4j_handler', 'Neo4jHandler'),
            ('qwen_generator', 'generate_analysis'),
            ('markdown_generator', 'generate_markdown'),
            ('markdown_generator', 'save_markdown'),
            ('ansible_executor', 'execute_solution_code'),
            ('ansible_executor', 'extract_solution1_code'),
            ('knowledge_loader', 'load_script_by_code_id'),
            ('graphrag_retriever', 'GraphRAGRetriever')
        ]
        
        for module_name, class_name in modules_to_load:
            try:
                spec = importlib.util.spec_from_file_location(
                    module_name, 
                    os.path.join(roothealer_dir, f"{module_name}.py")
                )
                if spec and spec.loader:
                    module = importlib.util.module_from_spec(spec)
                    spec.loader.exec_module(module)
                    
                    # 设置全局变量
                    if module_name == 'fault_matcher':
                        FaultMatcher = getattr(module, 'FaultMatcher', None)
                    elif module_name == 'neo4j_handler':
                        Neo4jHandler = getattr(module, 'Neo4jHandler', None)
                    elif module_name == 'qwen_generator':
                        generate_analysis = getattr(module, 'generate_analysis', None)
                    elif module_name == 'markdown_generator':
                        generate_markdown = getattr(module, 'generate_markdown', None)
                        save_markdown = getattr(module, 'save_markdown', None)
                    elif module_name == 'ansible_executor':
                        execute_solution_code = getattr(module, 'execute_solution_code', None)
                        extract_solution1_code = getattr(module, 'extract_solution1_code', None)
                    elif module_name == 'knowledge_loader':
                        load_script_by_code_id = getattr(module, 'load_script_by_code_id', None)
                    elif module_name == 'graphrag_retriever':
                        GraphRAGRetriever = getattr(module, 'GraphRAGRetriever', None)
                        
            except Exception as module_e:
                logger.warning(f"动态导入 {module_name}.{class_name} 失败: {module_e}")
        
        # 检查是否至少导入了一些关键模块
        if FaultMatcher is not None and Neo4jHandler is not None:
            logger.info("✅ 成功导入roothealer模块（方法2）")
        else:
            logger.error("❌ 两种导入方法都失败")
            
    except Exception as e2:
        logger.error(f"方法2导入失败: {e2}")
        logger.error(f"roothealer目录路径: {roothealer_dir}")
        logger.error(f"当前工作目录: {os.getcwd()}")
        
        # 尝试列出目录内容进行调试
        try:
            if os.path.exists(roothealer_dir):
                files = os.listdir(roothealer_dir)
                logger.info(f"roothealer目录内容: {files}")
            else:
                logger.error(f"roothealer目录不存在: {roothealer_dir}")
        except Exception as list_e:
            logger.error(f"无法列出目录内容: {list_e}")


class AutoFaultAnalyzer:
    """自动故障分析器"""
    
    def __init__(self):
        """初始化自动故障分析器"""
        self.logger = logging.getLogger(__name__)
        self.matcher = None
        self.neo4j = None
        self.rag_retriever = None
        self._initialize_components()
    
    def _initialize_components(self):
        """初始化各个组件"""
        try:
            if FaultMatcher is None:
                raise ImportError("FaultMatcher模块未正确导入")
            
            self.matcher = FaultMatcher()
            self.neo4j = Neo4jHandler()
            
            # 初始化GraphRAG检索器（可选）
            if GraphRAGRetriever is not None:
                try:
                    self.rag_retriever = GraphRAGRetriever(graphrag_root_path="/root/graphrag-basic/ragtest")
                except Exception as e:
                    self.logger.warning(f"GraphRAG检索器初始化失败，将跳过增强步骤: {e}")
                    self.rag_retriever = None
            else:
                self.rag_retriever = None
                
        except Exception as e:
            self.logger.error(f"组件初始化失败: {e}")
            self.matcher = None
            self.neo4j = None
            self.rag_retriever = None
    
    def analyze_anomalies(self, anomalies: List[Dict], metrics: Dict, 
                         risk_level: int, report_id: str) -> Optional[str]:
        """
        分析检测到的异常，自动进行根因分析和修复
        
        Args:
            anomalies: 检测到的异常列表
            metrics: 系统指标数据
            risk_level: 风险等级
            report_id: 报告ID
            
        Returns:
            生成的根因分析报告文件路径，如果分析失败返回None
        """
        if not anomalies:
            self.logger.info("未检测到异常，跳过根因分析")
            return None
        
        # 检查组件是否已正确初始化
        if not self.matcher or not self.neo4j:
            self.logger.error("故障分析组件未正确初始化，无法进行分析")
            return None
        
        try:
            # 1. 将异常映射为故障类型
            fault_types = self._map_anomalies_to_fault_types(anomalies)
            if not fault_types:
                self.logger.warning("无法将异常映射为已知故障类型")
                return None
            
            # 2. 选择最严重的故障类型进行分析
            primary_fault = self._select_primary_fault(fault_types, risk_level)
            self.logger.info(f"选择主要故障类型进行分析: {primary_fault}")
            
            # 3. 执行根因分析流程
            analysis_result = self._perform_root_cause_analysis(
                primary_fault, anomalies, metrics, report_id
            )
            
            return analysis_result
            
        except Exception as e:
            self.logger.error(f"自动故障分析失败: {e}")
            return None
    
    def _map_anomalies_to_fault_types(self, anomalies: List[Dict]) -> List[str]:
        """
        将异常映射为故障类型
        
        Args:
            anomalies: 异常列表
            
        Returns:
            故障类型列表
        """
        self.logger.info(f"开始映射异常，共 {len(anomalies)} 个异常")
        
        # 打印异常详情用于调试
        for i, anomaly in enumerate(anomalies):
            self.logger.info(f"异常 {i+1}: {anomaly}")
        
        fault_types = []
        seen_faults = set()
        
        # 异常到故障类型的映射规则
        anomaly_mapping = {
            # CPU相关异常
            'cpu_usage': 'CPU使用率过高（负载过高）',
            'cpu_per_core_usage': 'CPU使用率过高（负载过高）',
            'cpu_temperature': 'CPU使用率过高（负载过高）',
            'cpu_load': 'CPU使用率过高（负载过高）',
            
            # 内存相关异常
            'memory_usage': '内存泄漏（对象未释放）',
            'memory_available': '内存泄漏（对象未释放）',
            'memory_swap': '内存泄漏（对象未释放）',
            'memory_leak': '内存泄漏（对象未释放）',
            'redis_memory': '内存泄漏（对象未释放）',
            
            # 磁盘相关异常
            'disk_usage': '磁盘空间耗尽（No space left）',
            'disk_read_bytes': '磁盘空间耗尽（No space left）',
            'disk_write_bytes': '磁盘空间耗尽（No space left）',
            'disk_io_wait': '磁盘空间耗尽（No space left）',
            'disk_space': '磁盘空间耗尽（No space left）',
            
            # MySQL相关异常
            'mysql_service': 'MySQL死锁（资源竞争）',
            'mysql_connection': 'MySQL死锁（资源竞争）',
            'mysql_deadlock': 'MySQL死锁（资源竞争）',
            'database_connection': 'MySQL死锁（资源竞争）',
            
            # Web服务器相关异常
            'apache_service': 'Apache线程池耗尽（无法处理新请求）',
            'nginx_service': 'Apache线程池耗尽（无法处理新请求）',
            'nginx_status': 'Apache线程池耗尽（无法处理新请求）',
            'nginx_connections': 'Apache线程池耗尽（无法处理新请求）',
            'apache_threads': 'Apache线程池耗尽（无法处理新请求）',
            'nginx_5xx_errors': 'Apache线程池耗尽（无法处理新请求）',
            'apache_5xx_errors': 'Apache线程池耗尽（无法处理新请求）',
            
            # NPU相关异常
            'npu_usage': 'NPU资源耗尽（计算资源不足）',
            'npu_temperature': 'NPU资源耗尽（计算资源不足）',
            'npu_memory': 'NPU资源耗尽（计算资源不足）',
            
            # 调用链相关异常
            'slow_call': '慢调用检测（性能问题）',
            'failed_call': '调用失败（服务不可用）',
        }
        
        for anomaly in anomalies:
            anomaly_type = anomaly.get('type', '')
            self.logger.info(f"处理异常类型: '{anomaly_type}'")
            
            # 直接映射
            if anomaly_type in anomaly_mapping:
                fault_type = anomaly_mapping[anomaly_type]
                if fault_type not in seen_faults:
                    fault_types.append(fault_type)
                    seen_faults.add(fault_type)
                    self.logger.info(f"✅ 直接映射成功: '{anomaly_type}' -> '{fault_type}'")
            
            # 模糊匹配（处理复合类型）
            for key, fault_type in anomaly_mapping.items():
                if key in anomaly_type.lower() and fault_type not in seen_faults:
                    fault_types.append(fault_type)
                    seen_faults.add(fault_type)
                    self.logger.info(f"✅ 模糊匹配成功: '{anomaly_type}' -> '{fault_type}'")
        
        self.logger.info(f"映射结果: {fault_types}")
        return fault_types
    
    def _select_primary_fault(self, fault_types: List[str], risk_level: int) -> str:
        """
        选择主要故障类型进行分析
        
        Args:
            fault_types: 故障类型列表
            risk_level: 风险等级
            
        Returns:
            主要故障类型
        """
        # 根据风险等级和故障严重程度选择
        priority_order = [
            'MySQL死锁（资源竞争）',
            'Apache线程池耗尽（无法处理新请求）',
            'NPU资源耗尽（计算资源不足）',
            '磁盘空间耗尽（No space left）',
            '内存泄漏（对象未释放）',
            'CPU使用率过高（负载过高）',
            '慢调用检测（性能问题）',
            '调用失败（服务不可用）',
        ]
        
        # 按优先级排序
        for fault_type in priority_order:
            if fault_type in fault_types:
                return fault_type
        
        # 如果没有匹配到，返回第一个
        return fault_types[0] if fault_types else "未知故障"
    
    def _perform_root_cause_analysis(self, fault_type: str, anomalies: List[Dict], 
                                   metrics: Dict, report_id: str) -> Optional[str]:
        """
        执行根因分析流程
        
        Args:
            fault_type: 故障类型
            anomalies: 异常列表
            metrics: 系统指标
            report_id: 报告ID
            
        Returns:
            生成的报告文件路径
        """
        try:
            # 1. 匹配标准故障类型
            standard_fault = self.matcher.match_standard_fault(fault_type)
            if not standard_fault:
                self.logger.warning(f"无法在知识图谱中匹配到标准故障类型: {fault_type}")
                return None
            
            self.logger.info(f"检测到故障: {fault_type} → 匹配到标准故障: {standard_fault}")
            
            # 2. 获取标准故障的详细信息
            fault_details = self.matcher.get_fault_details(standard_fault)
            if not fault_details:
                self.logger.warning(f"无法获取标准故障的详细信息: {standard_fault}")
                return None
            
            # 3. 从知识图谱获取更多故障关联信息
            fault_info = self.neo4j.query_fault_info(standard_fault)
            
            # 4. 查询上游故障信息
            dependencies = fault_info.get('dependencies', [])
            upstream_faults = []
            seen_faults = set()
            for dep in dependencies:
                upstream_entity = dep["upstream_entity"]
                current_upstream_faults = self.neo4j.query_upstream_fault_info(upstream_entity)
                for fault in current_upstream_faults:
                    if fault["fault_name"] not in seen_faults:
                        seen_faults.add(fault["fault_name"])
                        upstream_faults.append(fault)
            
            # 5. GraphRAG检索增强知识
            if self.rag_retriever:
                try:
                    enhanced_knowledge = self.rag_retriever.retrieve_enhanced_knowledge(standard_fault)
                    if enhanced_knowledge:
                        fault_info["enhanced_knowledge"] = enhanced_knowledge
                    else:
                        fault_info["enhanced_knowledge"] = "【GraphRAG增强知识】未检索到相关补充信息"
                except Exception as e:
                    self.logger.warning(f"GraphRAG检索失败: {e}")
                    fault_info["enhanced_knowledge"] = "【GraphRAG增强知识】检索模块初始化失败，未获取补充信息"
            else:
                fault_info["enhanced_knowledge"] = "【GraphRAG增强知识】检索模块未初始化，未获取补充信息"
            
            # 6. 调用大模型生成解决方案
            self.logger.info("开始生成故障分析报告...")
            if generate_analysis is None:
                self.logger.error("generate_analysis函数未正确导入")
                return None
            analysis = generate_analysis(standard_fault, fault_details, fault_info, upstream_faults)
            
            # 7. 执行脚本和生成报告
            execution_result = ""
            script_content = None
            is_predefined = False
            
            if fault_info.get("fix_code_id"):
                if load_script_by_code_id is not None:
                    script_content = load_script_by_code_id(fault_info["fix_code_id"])
                    if script_content:
                        self.logger.info("✅ 成功加载预定义脚本")
                        is_predefined = True
                    else:
                        self.logger.warning("❌ 预定义脚本加载失败")
                else:
                    self.logger.warning("❌ load_script_by_code_id函数未正确导入")
            
            if not script_content:
                if extract_solution1_code is not None:
                    script_content = extract_solution1_code(analysis)
                    if not script_content:
                        execution_result = "❌ 无法获取修复脚本，未执行修复任务"
                        self.logger.warning(execution_result)
                else:
                    self.logger.warning("❌ extract_solution1_code函数未正确导入")
            
            if script_content:
                self.logger.info("开始执行修复脚本...")
                try:
                    if execute_solution_code is not None:
                        execution_result = execute_solution_code(
                            fault_name=standard_fault,
                            shell_code=script_content,
                            inventory="localhost"
                        )
                        self.logger.info("✅ 脚本执行完成")
                    else:
                        execution_result = "❌ execute_solution_code函数未正确导入"
                        self.logger.error(execution_result)
                except Exception as e:
                    error_msg = f"❌ 执行过程错误：{str(e)}"
                    execution_result = error_msg
                    self.logger.error(error_msg)
            
            # 8. 生成并保存Markdown报告
            if generate_markdown is not None and save_markdown is not None:
                md_content = generate_markdown(
                    standard_fault,
                    analysis,
                    execution_result,
                    script_content
                )
            else:
                self.logger.error("❌ generate_markdown或save_markdown函数未正确导入")
                return None
            
            # 添加检测上下文信息
            md_content = self._enhance_report_with_context(md_content, anomalies, metrics, report_id)
            
            report_path = save_markdown(md_content)
            
            if report_path:
                self.logger.info(f"✅ 自动故障分析完成，报告已保存: {report_path}")
                return report_path
            else:
                self.logger.error("❌ 报告保存失败")
                return None
                
        except Exception as e:
            self.logger.error(f"根因分析流程执行失败: {e}")
            return None
    
    def _enhance_report_with_context(self, md_content: str, anomalies: List[Dict], 
                                   metrics: Dict, report_id: str) -> str:
        """
        在报告中添加上下文信息
        
        Args:
            md_content: 原始Markdown内容
            anomalies: 异常列表
            metrics: 系统指标
            report_id: 报告ID
            
        Returns:
            增强后的Markdown内容
        """
        # 在报告开头添加上下文信息
        context_section = f"""
## 检测上下文信息

### 检测报告ID
{report_id}

### 检测时间
{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}

### 检测到的异常
"""
        
        for i, anomaly in enumerate(anomalies, 1):
            context_section += f"""
{i}. **{anomaly.get('type', '未知类型')}**
   - 级别: {anomaly.get('level', '未知')}
   - 消息: {anomaly.get('message', '无消息')}
   - 当前值: {anomaly.get('value', '未知')}
   - 阈值: {anomaly.get('threshold', '未知')}
"""
        
        context_section += f"""

### 系统指标快照
- CPU使用率: {metrics.get('cpu', {}).get('usage_percent', 'N/A')}%
- 内存使用率: {metrics.get('memory', {}).get('usage_percent', 'N/A')}%
- 磁盘使用率: {metrics.get('disk', {}).get('usage_percent', 'N/A')}%
- 网络状态: {'正常' if metrics.get('network', {}).get('status') == 'up' else '异常'}

---

"""
        
        # 将上下文信息插入到报告开头
        lines = md_content.split('\n')
        title_index = -1
        for i, line in enumerate(lines):
            if line.startswith('# 故障分析报告'):
                title_index = i
                break
        
        if title_index >= 0:
            lines.insert(title_index + 1, context_section)
            return '\n'.join(lines)
        else:
            return context_section + md_content
